看了怎麼建立 migration 檔案之後,今天我們來看 Laravel 怎麼建立和資料庫的連線。
如各位所知道的,Laravel 有許多資料庫的選擇,我們很難在短時間內看完所有連線資料庫的方式。所以我們先選定目標:看 Laravel 對 MySQL 的連線方式是怎麼建立的。選定目標之後再往下追,所需要看的方向就會比較明確了。
首先,根據我們對 Laravel 的理解,我們知道資料庫連線相關的內容應該是在 Illuminate\Database
裡面,所以對應的資料夾是 laravel/framework/src/Illuminate/Database
。
我們看到裡面的 DatabaseServiceProvider
/**
* Register the service provider. * * @return void
*/public function register()
{
Model::clearBootedModels();
$this->registerConnectionServices();
$this->registerFakerGenerator();
$this->registerQueueableEntityResolver();
}
這裡面會透過 registerConnectionServices()
建立連線相關的物件
protected function registerConnectionServices()
{
// The connection factory is used to create the actual connection instances on
// the database. We will inject the factory into the manager so that it may
// make the connections while they are actually needed and not of before.
$this->app->singleton('db.factory', function ($app) {
return new ConnectionFactory($app);
});
// The database manager is used to resolve various connections, since multiple
// connections might be managed. It also implements the connection resolver
// interface which may be used by other components requiring connections.
$this->app->singleton('db', function ($app) {
return new DatabaseManager($app, $app['db.factory']);
});
$this->app->bind('db.connection', function ($app) {
return $app['db']->connection();
});
$this->app->bind('db.schema', function ($app) {
return $app['db']->connection()->getSchemaBuilder();
});
$this->app->singleton('db.transactions', function ($app) {
return new DatabaseTransactionsManager;
});
}
這邊的 ConnectionFactory
作用如名稱所述,用來建立各種 Connection
物件。
/**
* Establish a PDO connection based on the configuration.
*
* @param array $config
* @param string|null $name
* @return \Illuminate\Database\Connection
*/
public function make(array $config, $name = null)
{
$config = $this->parseConfig($config, $name);
if (isset($config['read'])) {
return $this->createReadWriteConnection($config);
}
return $this->createSingleConnection($config);
}
這邊我們選 createSingleConnection()
往下研究
/**
* Create a single database connection instance.
*
* @param array $config
* @return \Illuminate\Database\Connection
*/
protected function createSingleConnection(array $config)
{
$pdo = $this->createPdoResolver($config);
return $this->createConnection(
$config['driver'], $pdo, $config['database'], $config['prefix'], $config
);
}
這邊我們看到 createSingleConnection()
只做兩件事情:建立 PDO Resolver 和建立連線。
首先我們看 createPdoResolver()
/**
* Create a new Closure that resolves to a PDO instance.
*
* @param array $config
* @return \Closure
*/
protected function createPdoResolver(array $config)
{
return array_key_exists('host', $config)
? $this->createPdoResolverWithHosts($config)
: $this->createPdoResolverWithoutHosts($config);
}
連線 MySQL 會有 Host
,所以我們追 createPdoResolverWithHosts
/**
* Create a new Closure that resolves to a PDO instance with a specific host or an array of hosts. * * @param array $config
* @return \Closure
* * @throws \PDOException
*/protected function createPdoResolverWithHosts(array $config)
{
return function () use ($config) {
foreach (Arr::shuffle($this->parseHosts($config)) as $host) {
$config['host'] = $host;
try {
return $this->createConnector($config)->connect($config);
} catch (PDOException $e) {
continue;
}
}
if (isset($e)) {
throw $e;
}
};
}
找到對應 $host
之後,我們進入到 $this->createConnector()
/**
* Create a connector instance based on the configuration.
*
* @param array $config
* @return \Illuminate\Database\Connectors\ConnectorInterface
*
* @throws \InvalidArgumentException
*/
public function createConnector(array $config)
{
if (! isset($config['driver'])) {
throw new InvalidArgumentException('A driver must be specified.');
}
if ($this->container->bound($key = "db.connector.{$config['driver']}")) {
return $this->container->make($key);
}
return match ($config['driver']) {
'mysql' => new MySqlConnector,
'mariadb' => new MariaDbConnector,
'pgsql' => new PostgresConnector,
'sqlite' => new SQLiteConnector,
'sqlsrv' => new SqlServerConnector,
default => throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]."),
};
}
到了這邊我們看到支援的所有 Connector
,如前面所說,我們選 MySqlConnector
往下看
/**
* Establish a database connection.
*
* @param array $config
* @return \PDO
*/
public function connect(array $config)
{
$dsn = $this->getDsn($config);
$options = $this->getOptions($config);
// We need to grab the PDO options that should be used while making the brand
// new connection instance. The PDO options control various aspects of the
// connection's behavior, and some might be specified by the developers.
$connection = $this->createConnection($dsn, $config, $options);
if (! empty($config['database'])) {
$connection->exec("use `{$config['database']}`;");
}
$this->configureConnection($connection, $config);
return $connection;
}
這邊開始會用到 Connector
內的方法了,接著我們看 Connector::createConnection()
的實做
public function createConnection($dsn, array $config, array $options)
{
[$username, $password] = [
$config['username'] ?? null, $config['password'] ?? null,
];
try {
return $this->createPdoConnection(
$dsn, $username, $password, $options
);
} catch (Exception $e) {
return $this->tryAgainIfCausedByLostConnection(
$e, $dsn, $username, $password, $options
);
}
}
createPdoConnection()
實作則是
protected function createPdoConnection($dsn, $username, $password, $options)
{
return new PDO($dsn, $username, $password, $options);
}
到這邊,我們總算追到了 Laravel 建立連線的方式:使用 PHP 的 PDO 物件,以及宣告的位置在哪。
今天我們先看到這邊。